﻿//MsChartEx.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Xaml
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Resources;
	using System.Reflection;
	using System.Text;
	using System.Windows.Forms;
	using System.Windows.Forms.DataVisualization.Charting;
	using WinChartArea = System.Windows.Forms.DataVisualization.Charting.ChartArea;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Reflection;

	using Wintellect.PowerCollections;

	class MsChartEx : System.Windows.Forms.DataVisualization.Charting.Chart
	{
		private sealed class ChartResourceManager : ResourceManager
		{
			private readonly ResourceManager _inner;

			public ChartResourceManager(ResourceManager inner)
			{
				_inner = inner;
			}

			public override string GetString(string name, CultureInfo culture)
			{
				if (name == "FormatErrorString")
					return "#Error";

				return _inner.GetString(name, culture);
			}
		}

		static MsChartEx()
		{
			var type = typeof(System.Windows.Forms.DataVisualization.Charting.Chart).AssemblyQualifiedName.Replace(".Chart,", ".SR+Keys,").To<Type>();
			type.SetValue<ResourceManager>("resourceManager", new ChartResourceManager(type.GetValue<VoidType, ResourceManager>("resourceManager", null)));
		}

		private WinChartArea _capturedArea;
		private WinChartArea _nearbyArea;
		private int _capturedY;
		private float _capturedAreaHeight;
		private float _nearbyAreaHeight;
		private float _nearbyAreaY;

		public event Action AreaSizeChanged;

		public const int AreaMargin = 5;

		public float ToRelativeX(float x)
		{
			return x * 100.0f / Width;
		}

		public float ToRelativeY(float y)
		{
			return y * 100.0f / Height;
		}

		private WinChartArea ChechMousePosition(MouseEventArgs e, out int i)
		{
			if (ChartAreas.Count > 1)
			{
				var result = HitTest(e.X, e.Y);

				if ((result.ChartElementType == ChartElementType.Axis && result.Axis == result.ChartArea.AxisX) || result.ChartElementType == ChartElementType.Nothing)
				{
					var area = result.ChartArea;

					if (area == null)
					{
						var x = ToRelativeX(e.X);
						var y = ToRelativeY(e.Y);

						for (var j = 0; j < ChartAreas.Count - 1; j++)
						{
							var a = ChartAreas[j];
							if (x > a.Position.X && x < a.Position.Right && y > a.Position.Bottom && y < ChartAreas[j + 1].Position.Y)
							{
								area = a;
								break;
							}
						}
					}

					if (area != null)
					{
						i = ChartAreas.IndexOf(area);

						if (i < (ChartAreas.Count - 1))
						{
							return area;
						}
					}
				}
			}
			
			i = -1;
			return null;
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown(e);

			int i;
			var area = ChechMousePosition(e, out i);
			if (area != null)
			{
				Cursor = Cursors.HSplit;

				_capturedArea = area;
				_nearbyArea = ChartAreas[i + 1];
				_capturedY = e.Y;
				_capturedAreaHeight = _capturedArea.Position.Height;
				_nearbyAreaHeight = _nearbyArea.Position.Height;
				_nearbyAreaY = _nearbyArea.Position.Y;
			}
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp(e);

			if (_capturedArea != null)
			{
				_capturedArea = null;
				Cursor = Cursors.Default;
				AreaSizeChanged.SafeInvoke();
			}
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);

			if (_capturedArea != null)
			{
				var delta = ToRelativeY(e.Y - _capturedY);

				const int minHeight = 10;

				if ((_nearbyAreaHeight - delta) > minHeight && (_capturedAreaHeight + delta) > minHeight)
				{
					_capturedArea.Position.Height = _capturedAreaHeight + delta;
					_nearbyArea.Position.Height = _nearbyAreaHeight - delta;
					_nearbyArea.Position.Y = _nearbyAreaY + delta;

					//System.Diagnostics.Debug.WriteLine("Delta {0} H1 = {1} Y2 = {2} H2 = {3}".Put(delta, _capturedArea.Position.Height, _nearbyArea.Position.Y, _nearbyArea.Position.Height));
				}
			}
			else
			{
				int i;
				var result = ChechMousePosition(e, out i);
				Cursor = result != null ? Cursors.HSplit : Cursors.Default;
			}
		}

		private sealed class FastDataPointList : BaseList<DataPoint>
		{
			private readonly Dictionary<DataPoint, int> _indecies = new Dictionary<DataPoint, int>();

			public bool IsClearing { get; set; }

			public override int IndexOf(DataPoint item)
			{
				var index = _indecies.TryGetValue2(item);
				return index != null ? (int)index : -1;
			}

			protected override void OnAdded(DataPoint item)
			{
				base.OnAdded(item);
				_indecies.Add(item, Count - 1);
			}

			protected override void OnInserted(int index, DataPoint item)
			{
				base.OnInserted(index, item);

				foreach (var point in _indecies.Keys.ToArray())
				{
					if (_indecies[point] >= index)
						_indecies[point]++;
				}

				_indecies[item] = index;
			}

			protected override void OnRemoved(DataPoint item)
			{
				base.OnRemoved(item);

				var index = IsClearing ? -1 : IndexOf(item);

				_indecies.Remove(item);

				if (!IsClearing && index >= 0)
				{
					foreach (var point in _indecies.Keys.ToArray())
					{
						if (_indecies[point] > index)
							_indecies[point]--;
					}
				}
			}

			protected override void OnCleared()
			{
				base.OnCleared();
				_indecies.Clear();
			}
		}

		private readonly MultiDictionary<WinChartArea, Series> _series = new MultiDictionary<WinChartArea, Series>(false);

		public void AddSeries(WinChartArea area, Series series)
		{
			area.AxisX.Interval = 0;

			// NOTE mika ускоряет индекированный доступ к точкам
			series.Points.SetValue("items", new FastDataPointList());
			series.GetValue<Series, VoidType, DataPointCollection>("fakeDataPoints", null).SetValue("items", new FastDataPointList());

			_series.Add(area, series);
			Series.Add(series);
		}

		public void RemoveSeries(WinChartArea area, Series series)
		{
			ClearPoints(series);
			_series.Remove(area, series);
			Series.Remove(series);
		}

		public IEnumerable<Series> GetSeries(WinChartArea area)
		{
			return _series[area];
		}

		public void ClearPoints(Series series)
		{
			var fastPoints1 = series.Points.GetValue<DataPointCollection, VoidType, FastDataPointList>("items", null);
			var fastPoints2 = series.GetValue<Series, VoidType, DataPointCollection>("fakeDataPoints", null).GetValue<DataPointCollection, VoidType, FastDataPointList>("items", null);

			fastPoints1.IsClearing = fastPoints2.IsClearing = true;

			try
			{
				series.Points.Clear();
			}
			finally
			{
				fastPoints1.IsClearing = fastPoints2.IsClearing = false;
			}
		}

        static string DecodeSettings(string encoded) {
            return Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
        }


        static bool _chartInitialized;

        internal static void InitChartSettings() {
            if(_chartInitialized) return;

            _chartInitialized = true;

            try {
                var a = DecodeSettings("QWJ0LkNvbnRyb2xzLlNjaUNoYXJ0LldwZg==");
                var c = DecodeSettings("QWJ0LkNvbnRyb2xzLlNjaUNoYXJ0LlNjaUNoYXJ0U3VyZmFjZQ==");
                var m = DecodeSettings("U2V0TGljZW5zZUtleQ==");
                var p = DecodeSettings("bHdBQUFBRUFBQUMvS0ZKc3E5ak5BVkVBUTNWemRHOXRaWEk5YzNSdlkydHphR0Z5Y0NBN1QzSmtaWEpKWkQxQlFsUXhNakV3TWpRdE16STRNeTAwTnpFd01UdFRkV0p6WTNKcGNIUnBiMjVXWVd4cFpGUnZQVEkwTFU5amRDMHlNREV6aGhiejYwNmlnOFRvd0s0RHF1OVhnQjdrWHV1T2FGb0xYWUpjcmhtRFAzZ3BDZGhVbkxXT2dFbkVPMjdwUnNLZg==");

                var type = Assembly.Load(new AssemblyName {Name = a}).GetType(c);

                var instance = Activator.CreateInstance(type);
                var method = type.GetMethod(m);
                method.Invoke(instance, new object[] {p});
            } catch {}
        }
	}
}